﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Synchronization;
using log4net;
using SyncLib.Interface;
using System.IO;
using SyncLib;
using SyncLib.Data;
using Microsoft.Synchronization.Data;
using System.ServiceModel;
using SyncLib.Client;

namespace SyncApplication
{
    public class KSyncProviderProxy : KnowledgeSyncProvider
    {
        static ILog Log = LogManager.GetLogger(typeof(Log4NetImplement));

        ISyncManager Proxy
        {
            get { return CacheProxyInfo.Channel; }
        }
        ProxyInfo<ISyncManager> CacheProxyInfo = null;
        private DirectoryInfo localBatchingDirectory;
        private string batchingDirectory = Environment.ExpandEnvironmentVariables("%TEMP%");
        SyncIdFormatGroup IdFormart;

        ~KSyncProviderProxy()
        {
            CacheProxyInfo = null;
            localBatchingDirectory = null;
        }

        //  public KeeperSyncSession KPSyncSession { get; set; }
        public string BatchingDirectory
        {
            //get { return batchingDirectory; }
            get
            {
                return CommonVariable.SyncFolderBatch;
            }
            //set
            //{
            //    if (string.IsNullOrEmpty(value))
            //    {
            //        throw new ArgumentException("value cannot be null or empty");
            //    }
            //    try
            //    {
            //        Uri uri = new Uri(value);
            //        if (!uri.IsFile || uri.IsUnc)
            //        {
            //            throw new ArgumentException("value must be a local directory");
            //        }
            //        batchingDirectory = value;
            //    }
            //    catch (Exception e)
            //    {
            //        throw new ArgumentException("Invalid batching directory.", e);
            //    }
            //}
        }

        public KSyncProviderProxy(ProxyInfo<ISyncManager> proxyInfo)
        {
            CacheProxyInfo = proxyInfo;
        }
        public void Initialize(ScopeInfoEx scope)
        {
            try
            {
                SyncBatchInfo.Reset();
                Log.Info("\n*********************************************************");
                Log.Info("************ Call Remote Initialize:" + scope.ScopeName + "*************");
                Log.Info("\n*********************************************************");


                Proxy.Initialize(scope, SyncProviderPosition.Remote);
                IdFormart = Proxy.GetIdFormats();
            }
            catch (FaultException<WebSyncFaultException> fe)
            {
                Log.Fatal("Remote Initialize WebSyncFaultException:", fe);
                throw fe;
            }
            catch (Exception ex)
            {
                Log.Fatal("Remote Initialize Exception :", ex);
                throw ex;
            }
        }
        public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
        {
            try
            {
                Log.Info("*****************************************************************");
                Log.Info("******************** New Sync Session ***************************");
                Log.Info("*****************************************************************");
                Log.InfoFormat("Begin Call BeginSession with Position:" + position.ToString());

                Proxy.BeginSession(position);
            }
            catch (Exception ex)
            {
                Log.Fatal("BeginSession:", ex);
                throw ex;
            }
        }

        public override void EndSession(SyncSessionContext syncSessionContext)
        {
            try
            {
                Log.InfoFormat("Begin call EndSession");

                Proxy.EndSession();
                CleanBatchFolder();
                Log.Info("EndSession");
            }
            catch (Exception ex)
            {
                Log.Fatal("EndSession:", ex);
                throw;
            }
        }

        private void CleanBatchFolder()
        {
            try
            {
                if (CommonVariable.CleanBatch)
                {
                    if (localBatchingDirectory != null && localBatchingDirectory.Exists)
                        localBatchingDirectory.Delete(true);
                    if (Directory.Exists(CommonVariable.SyncFolderBatch + "\\" + CommonVariable.Get(ClientConst.ClientID).ToString()))
                        Directory.Delete(CommonVariable.SyncFolderBatch + "\\" +CommonVariable.Get(ClientConst.ClientID).ToString(), true);
                }
            }
            catch (Exception ex)
            {
                Log.Fatal("Can't Delete Batch folder", ex);
            }
        }

        public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
        {
           changeDataRetriever = null;

           return InternalGetChangeBatch(batchSize, destinationKnowledge, out changeDataRetriever);
           // int i=0;
           //SyncKnowledge temp=  destinationKnowledge.Clone();
           // while(i<2)
           // try
           // {
           //     return GetChangeBatch(batchSize, temp,out changeDataRetriever);
           // }
           // catch
           // {
           //     throw;
           // }
           // return null;
        }

        private ChangeBatch InternalGetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
        {
            try
            {
                Log.InfoFormat("Begin Call GetChangeBatch with batchSize:{0}", batchSize);
                
                KChangesParameter batch = Proxy.GetChangeBatch(batchSize, destinationKnowledge);

                changeDataRetriever = batch.DataRetriever;
                DbSyncContext context = changeDataRetriever as DbSyncContext;

                //Check to see if the data is batched.
                if (context != null && context.IsDataBatched)
                {
                    Log.Info("DataBatched Enable, try get batch file");
                    if (this.localBatchingDirectory == null)
                    {
                        //Retrieve the remote peer id from the MadeWithKnowledge.ReplicaId. MadeWithKnowledge is the local knowledge of the peer 
                        //that is enumerating the changes.
                        string remotePeerId = context.MadeWithKnowledge.ReplicaId.ToString();

                        //Generate a unique Id for the directory.
                        //We use the peer id of the store enumerating the changes so that the local temp directory is same for a given source
                        //across sync sessions. This enables us to restart a failed sync by not downloading already received files.
                        string sessionDir = Path.Combine(this.batchingDirectory, "WebSync_" + remotePeerId);
                        this.localBatchingDirectory = new DirectoryInfo(sessionDir);
                        //Create the directory if it doesnt exist.
                        if (!this.localBatchingDirectory.Exists)
                        {
                            this.localBatchingDirectory.Create();
                        }
                    }

                    string localFileName = Path.Combine(this.localBatchingDirectory.FullName, context.BatchFileName);
                    FileInfo localFileInfo = new FileInfo(localFileName);

                    MonitorEvent.Monitor.Info("Download Batch Index : " + ++SyncBatchInfo.Downloaded);
                    //Download the file only if doesnt exist                
                    if (!localFileInfo.Exists)
                    {
                        Log.InfoFormat("DownloadBatchFile, FileName:{0}", context.BatchFileName);
                        MonitorEvent.Monitor.Info("Download Batch data:" + SyncBatchInfo.Downloaded);

                        byte[] remoteFileContents = this.Proxy.DownloadBatchFile(context.BatchFileName);
                        Log.InfoFormat("DownloadBatchFile Success, Data lenght:{0}", remoteFileContents.LongLength);

                        Log.InfoFormat("Write Remote BatchFile to local disk FileName:{0}", localFileName);
                        using (FileStream localFileStream = new FileStream(localFileName, FileMode.Create, FileAccess.Write))
                        {
                            localFileStream.Write(remoteFileContents, 0, remoteFileContents.Length);
                        }
                        Log.Info("Write Remote BatchFile  ==> Success");
                    }
                    //Set DbSyncContext.Batchfile name to the new local file name
                    context.BatchFileName = localFileName;
                }
                SyncBatchInfo.Total++;
                return batch.ChangeBatch;
            }

            catch (Exception ex)
            {
                Log.Fatal("GetChangeBatch :", ex);
                throw ex;

            }
        }
        public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
        {
            try
            {
                Log.InfoFormat("Begin Call GetFullEnumerationChangeBatch BatchSize:{0}", batchSize);
                KChangesParameter batch = Proxy.GetFullEnumerationChangeBatch(batchSize, lowerEnumerationBound, knowledgeForDataRetrieval);
                changeDataRetriever = batch.DataRetriever;
                DbSyncContext context = changeDataRetriever as DbSyncContext;
                //Check to see if the data is batched.
                if (context != null && context.IsDataBatched)
                {
                    Log.Info("DataBatched Enable, try get batch file");
                    if (this.localBatchingDirectory == null)
                    {
                        //Retrieve the remote peer id from the MadeWithKnowledge.ReplicaId. MadeWithKnowledge is the local knowledge of the peer 
                        //that is enumerating the changes.
                        string remotePeerId = context.MadeWithKnowledge.ReplicaId.ToString();

                        //Generate a unique Id for the directory.
                        //We use the peer id of the store enumerating the changes so that the local temp directory is same for a given source
                        //across sync sessions. This enables us to restart a failed sync by not downloading already received files.
                        string sessionDir = Path.Combine(this.batchingDirectory, "WebSync_" + remotePeerId);
                        this.localBatchingDirectory = new DirectoryInfo(sessionDir);
                        //Create the directory if it doesnt exist.
                        if (!this.localBatchingDirectory.Exists)
                        {
                            this.localBatchingDirectory.Create();
                        }
                    }

                    string localFileName = Path.Combine(this.localBatchingDirectory.FullName, context.BatchFileName);
                    FileInfo localFileInfo = new FileInfo(localFileName);

                    //Download the file only if doesnt exist                
                    if (!localFileInfo.Exists)
                    {
                        Log.InfoFormat("DownloadBatchFile, FileName:{0}", context.BatchFileName);
                        byte[] remoteFileContents = this.Proxy.DownloadBatchFile(context.BatchFileName);
                        Log.InfoFormat("DownloadBatchFile Success, Data lenght:{0}", remoteFileContents.LongLength);

                        Log.InfoFormat("Write Remote BatchFile to local disk FileName:{0}", localFileName);
                        using (FileStream localFileStream = new FileStream(localFileName, FileMode.Create, FileAccess.Write))
                        {
                            localFileStream.Write(remoteFileContents, 0, remoteFileContents.Length);
                        }
                        Log.Info("Write Remote BatchFile  ==> Success");
                    }
                    //Set DbSyncContext.Batchfile name to the new local file name
                    context.BatchFileName = localFileName;
                }

                return batch.FullChangeBatch;
            }
            catch (Exception ex)
            {
                Log.Fatal("GetFullEnumerationChangeBatch :", ex);
                throw ex;

            }
        }

        public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
        {
            try
            {
                Log.Info("Begin call GetSyncBatchParameters");
                KSyncBatchParameter batch = Proxy.GetSyncBatchParameters();
                batchSize = batch.BatchSize;
                knowledge = batch.DestinationKnowledge;
                Log.InfoFormat("Call GetSyncBatchParameters result BatchSize:{0}", batchSize);
            }
            catch (Exception ex)
            {
                Log.Fatal("GetSyncBatchParameters:", ex);
                throw ex;
            }

        }

        public override SyncIdFormatGroup IdFormats
        {
            get { return IdFormart; }
        }

        public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
        {
            try
            {
               
                Log.InfoFormat("Begin call ProcessChangeBatch resolutionPolicy:{0} ", resolutionPolicy);
                MonitorEvent.Monitor.Info("Lưu thay đổi(Máy chủ)");

                DbSyncContext context = changeDataRetriever as DbSyncContext;
                if (context != null && context.IsDataBatched)
                {
                    string fileName = new FileInfo(context.BatchFileName).Name;

                    //Retrieve the remote peer id from the MadeWithKnowledge.ReplicaId. MadeWithKnowledge is the local knowledge of the peer 
                    //that is enumerating the changes.
                    string peerId = context.MadeWithKnowledge.ReplicaId.ToString();

                    //Check to see if service already has this file
                    if (!this.Proxy.HasUploadedBatchFile(fileName, peerId))
                    {
                        //Upload this file to remote service
                        FileStream stream = new FileStream(context.BatchFileName, FileMode.Open, FileAccess.Read);
                        byte[] contents = new byte[stream.Length];
                        using (stream)
                        {
                            stream.Read(contents, 0, contents.Length);
                        }
                        this.Proxy.UploadBatchFile(fileName, contents, peerId);
                    }

                    context.BatchFileName = fileName;
                }


                SyncSessionStatistics remoteSessionStatistics = new SyncSessionStatistics();
                remoteSessionStatistics = Proxy.ProcessChangeBatch(resolutionPolicy, sourceChanges, changeDataRetriever, sessionStatistics);
                sessionStatistics.ChangesApplied += remoteSessionStatistics.ChangesApplied;
                sessionStatistics.ChangesFailed += remoteSessionStatistics.ChangesFailed;

                Log.InfoFormat("Finish ProcessChangeBatch ChangesApplied:{0} ChangesFailed:{1}", remoteSessionStatistics.ChangesApplied, remoteSessionStatistics.ChangesFailed);
            }
            catch (Exception ex)
            {
                Log.Fatal("ProcessChangeBatch :", ex);
                throw ex;

            }
        }

        public override void ProcessFullEnumerationChangeBatch(ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
        {
            try
            {
                Log.InfoFormat("Begin call ProcessFullEnumerationChangeBatch with resolutionPolicy:{0}", resolutionPolicy);
                SyncSessionStatistics remoteSessionStatistics = new SyncSessionStatistics();
                remoteSessionStatistics = Proxy.ProcessFullEnumerationChangeBatch(resolutionPolicy, sourceChanges, changeDataRetriever, sessionStatistics);
                sessionStatistics.ChangesApplied += remoteSessionStatistics.ChangesApplied;
                sessionStatistics.ChangesFailed += remoteSessionStatistics.ChangesFailed;

                Log.Info("Finish ProcessFullEnumerationChangeBatch");
            }
            catch (Exception ex)
            {
                Log.Fatal("ProcessFullEnumerationChangeBatch :", ex);
                throw;
            }

        }
    }

}
